# load required libraries

# to use harry potter dataset
# devtools::install_github("bradleyboehmke/harrypotter")
# devtools::install_github("quanteda/quanteda.sentiment")
# devtools::install_github("quanteda/quanteda.corpora")

library(quanteda)
library(readtext)
library(corpus)
library(tidyverse)
library(stringr)
library(tidytext)
library(harrypotter)
library(dplyr)
library(quanteda.sentiment)
library(vader)
library(caret)
library(reshape2)


require(quanteda)
require(quanteda.corpora)
require(quanteda.sentiment)
#library("quanteda", warn.conflicts = FALSE, quietly = TRUE)

1. Step: Load Data & Lexicons

# load datasets
reviews <- readRDS(file="datasets/red_review.rds")
twitter <- readRDS(file="datasets/red_twitter.rds")
parlvote <- readRDS(file="datasets/red_parl_vote.rds")

# load lexicon
afinn <- data_dictionary_AFINN
reviews
twitter
parlvote

2. Step: Perform Sentiment Analysis

3. Step: Calculate Statistics

# convert to binary results
get_binary <- function(dataframes, type){
  for(df in dataframes){
    df$afinn_binary[df$afinn > 0] <- "pos"
    df$afinn_binary[df$afinn <= 0] <- "neg"
        
    df$lsd_binary[df$lsd > 0] <- "pos"
    df$lsd_binary[df$lsd <= 0] <- "neg"
        
    df$vader_binary[df$vader > 0] <- "pos"
    df$vader_binary[df$vader <= 0] <- "neg"
        
    df$rating_binary[df$rating >= 3] <- "pos"
    df$rating_binary[df$rating < 3] <- "neg"
    if(type == "01"){
      df[df == "pos"] <- 1
      df[df == "neg"] <- 0
    }
  }
  return(dataframes)
}


x <- get_binary(list(reviews_sentiment, twitter_sentiment, parlvote_sentiment), "01")
reviews_sentiment$afinn_binary[reviews_sentiment$afinn > 0] <- "1"
reviews_sentiment$afinn_binary[reviews_sentiment$afinn <= 0] <- "0"
reviews_sentiment$lsd_binary[reviews_sentiment$lsd > 0] <- "1"
reviews_sentiment$lsd_binary[reviews_sentiment$lsd <= 0] <- "0"
reviews_sentiment$vader_binary[reviews_sentiment$vader > 0] <- "1"
reviews_sentiment$vader_binary[reviews_sentiment$vader <= 0] <- "0"
reviews_sentiment$rating_binary[reviews_sentiment$rating >= 3] <- "1"
reviews_sentiment$rating_binary[reviews_sentiment$rating < 3] <- "0"

twitter_sentiment$afinn_binary[twitter_sentiment$afinn > 0] <- "1"
twitter_sentiment$afinn_binary[twitter_sentiment$afinn <= 0] <- "0"
twitter_sentiment$lsd_binary[twitter_sentiment$lsd > 0] <- "1"
twitter_sentiment$lsd_binary[twitter_sentiment$lsd <= 0] <- "0"
twitter_sentiment$vader_binary[twitter_sentiment$vader > 0] <- "1"
twitter_sentiment$vader_binary[twitter_sentiment$vader <= 0] <- "0"
twitter_sentiment$rating_binary[twitter_sentiment$rating == "Positive"] <- "1"
twitter_sentiment$rating_binary[twitter_sentiment$rating == "Negative"] <- "0"
twitter_sentiment$rating_binary[twitter_sentiment$rating == "Neutral"] <- NA

parlvote_sentiment$afinn_binary[parlvote_sentiment$afinn > 0] <- "1"
parlvote_sentiment$afinn_binary[parlvote_sentiment$afinn <= 0] <- "0"
parlvote_sentiment$lsd_binary[parlvote_sentiment$lsd > 0] <- "1"
parlvote_sentiment$lsd_binary[parlvote_sentiment$lsd <= 0] <- "0"
parlvote_sentiment$vader_binary[parlvote_sentiment$vader > 0] <- "1"
parlvote_sentiment$vader_binary[parlvote_sentiment$vader <= 0] <- "0"

# optionally: convert 0 = negative, 1 = positive
#reviews_50[reviews_50 == "pos"] <- 1
#reviews_50[reviews_50 == "neg"] <- 0
get_statistics <- function(df) {
  #statistics <- data.frame(matrix(ncol=4, nrow=0))
  #x <- c("accuracy", "precision", "recall", "F1")
  #colnames(statistics) <- x
  statistics <- data.frame(matrix(ncol=1, nrow=0))
  colnames(statistics) <- "accuracy"
  
  lex1 <- "afinn_binary"
  lex2 <- "lsd_binary"
  lex3 <- "vader_binary"
  gold <- "rating_binary"
  
  lexicons <- c(lex1,lex2,lex3)
  
  for(lex in lexicons){
    cf <- confusionMatrix(factor(df[[lex]]),factor(df[[gold]]))
    accuracy <- cf[[3]][1]
    #confusion_matrix <- table(ACTUAL=df[[gold]], PREDICTED=df[[lex]])
    #TN <- confusion_matrix[1]
    #FN <- confusion_matrix[2]
    #FP <- confusion_matrix[3]
    #TP <- confusion_matrix[4]
    
    # calculate statistics
    #precision <- TP/(TP+FP)
    #accuracy <- (TP+TN)/(TP+TN+FP+FN)
    #recall <- TP/(TP+FN)
    #F1 <- (2*precision*recall)/(precision+recall)
  
    # add to table
    #output <- c(accuracy,precision, recall, F1)
    #statistics[lex,] = rbind(statistics[[lex]], output)
    statistics[lex,] = rbind(statistics[[lex]], accuracy)
    #print(output)
    }
    
  return(statistics)
  
}

review_stats <- get_statistics(reviews_sentiment)
#twitter_stats <- get_statistics(twitter_sentiment)
#parlvote_stats <- get_statistics(parlvote_sentiment)

review_stats

4. Step: Plot Data

# plot columns
reviews_dfm <- melt(head(reviews_sentiment,50)[,c('id','afinn','lsd','vader')],id.vars = 1)

reviews_plot <- ggplot(reviews_dfm,aes(x = id,y = value)) + 
                geom_bar(aes(fill = variable),stat = "identity",position = "dodge") +
                ggtitle("Reviews Sentiments")


twitter_dfm <- melt(head(twitter_sentiment,50)[,c('id','afinn','lsd','vader')],id.vars = 1)

twitter_plot <- ggplot(twitter_dfm,aes(x = id,y = value)) + 
                geom_bar(aes(fill = variable),stat = "identity",position = "dodge") +
                ggtitle("Twitter Sentiments")

parlvote_dfm <- melt(head(parlvote_sentiment,50)[,c('id','afinn','lsd','vader')],id.vars = 1)

parlvote_plot <- ggplot(parlvote_dfm,aes(x = id,y = value)) + 
                geom_bar(aes(fill = variable),stat = "identity",position = "dodge")+
                facet_wrap(~ variable, ncol = 1, scales="free_y")+
                ggtitle("ParlVote Sentiments")

reviews_plot

twitter_plot

parlvote_plot

ADDITIONAL

Harry Potter - Dataset

# load harry potter dataset 
titles <- c("Philosopher's Stone", "Chamber of Secrets", "Prisoner of Azkaban",
            "Goblet of Fire", "Order of the Phoenix", "Half-Blood Prince",
            "Deathly Hallows")

books <- list(philosophers_stone, chamber_of_secrets, prisoner_of_azkaban,
           goblet_of_fire, order_of_the_phoenix, half_blood_prince,
           deathly_hallows)
  
series <- tibble()

for(i in seq_along(titles)) {
        
        clean <- tibble(chapter = seq_along(books[[i]]),
                        text = books[[i]]) %>%
             #unnest_tokens(word, text) %>%
             mutate(book = titles[i]) %>%
             select(book, everything())

        series <- rbind(series, clean)
}

series$book <- factor(series$book, levels = rev(titles))

series
#book_groups <- series %>% group_by(book, chapter)

Harry Potter - AFINN Lexicon

afinn_hp2 <- series %>%
        group_by(book, chapter) %>% # add word for single word scores 
        inner_join(get_sentiments("afinn")) %>%
        group_by(book, chapter) %>% # add word for single word scores
        #summarise(sentiment = sum(value)) %>%
        summarise(sentiment = mean(value, na.rm = TRUE)) %>%
        mutate(method = "AFINN")  %>%
        ggplot(aes(chapter, sentiment, fill = book)) +
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE) +
          facet_wrap(~ book, ncol = 2, scales = "free_x")

afinn_hp2

#ggsave(plot = afinn, width = 15, height = 15, dpi = 300, filename = "afinn_hp_mean.png")

Lexicoder: HP


hp1_lsd.df <- as.data.frame.matrix(hp1_lsd)

hp1_lsd.df$chapter <- 1:nrow(hp1_lsd.df)

plot <- ggplot(hp1_lsd, aes(x =hp1_lsd.df$chapter, y=sentiment)) +
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE)
plot + ylim(-1.0, 1.0) + labs(y="sentiment", x = "chapter") + ggtitle("HP1 - Lexicoder")


#hp1_lsd.df

AFINN: HP

plot + ylim(-1.0, 1.0) + labs(y="sentiment", x = "chapter") + ggtitle("HP1 - AFINN")
Warnung: Use of `hp1_afinn.df$chapter` is discouraged. Use `chapter` instead.

VADER: HP

QUANTEDA.SENTIMENT

AFINN: HP

# Work with quanteda.sentiment on HP corpus:

# convert tibble to dataframe
series.df <- as.data.frame(series)

# tokenize books
series_tokenized <- series.df %>%
  unnest_tokens(tokens, text)

# apply afinn lexicon
series_tokenized$afinn2 <- textstat_valence(series_tokenized$tokens, afinn2)$sentiment

# replace all 0 values with na
series_tokenized[series_tokenized == 0] <- NA

series_tokenized %>%
  group_by(book, chapter) %>% # group df by book and chapter to get sentiment per chapter
  summarise(sentiment = mean(afinn2, na.rm = TRUE)) %>% # calculate mean w/o regarding na values
  mutate(method = "AFINN") %>% # add column with method 
        ggplot(aes(chapter, sentiment, fill = book)) + # plot sentiment of books
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE) +
          facet_wrap(~ book, ncol = 2, scales = "free_x") +
          ggtitle("AFINN HP")
`summarise()` has grouped output by 'book'. You can override using the `.groups` argument.

Lexicoder: HP

# Work with quanteda.sentiment on HP corpus:

# apply lexicoder lexicon
series$lsd <- textstat_polarity(tokens(series$text), data_dictionary_LSD2015)$sentiment 

#series.df <- as.data.frame(series)

plot <- ggplot(series, aes(chapter, lsd, fill = book)) + # plot sentiment of books
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE) +
          facet_wrap(~ book, ncol = 2, scales = "free_x") +
          ggtitle("Lexicoder HP")
plot 

Vader: HP

REVIEWS DATASET

# load dataset
reviews <- readtext("datasets/goodreads_reviews_children_2.json", text_field = "review_text")

# convert to dataframe
reviews.df <- as.data.frame(reviews)

# add doc_id (i.e. according to index)
reviews.df$doc_id <- 1:nrow(reviews.df)

Sample Dataset

# get random sample of 50 reviews 
reviews_sample <- reviews.df[sample(1:nrow(reviews.df), 50,
   replace=FALSE),]

# get first 50 rows of data 
reviews_50 <- head(reviews.df,50)
reviews_50 = subset(reviews_50, select = c(doc_id,text,rating))
reviews_50

Get Translations of Dataset

# either via corpus 
reviews.corpus <- corpus(reviews)
docvars(reviews.corpus, "language") <- textcat(reviews.corpus)
reviews_en <- corpus_subset(reviews.corpus, language == "english", drop_docid = TRUE)

# or via dataframe logic
reviews.df$language <- textcat(reviews.df$text)

Sentiment Analysis on Reviews Dataset

AFINN

# Afinn
# tokenize 
reviews_tokenized <- reviews_50 %>%
  unnest_tokens(tokens, text)

# apply afinn lexicon
reviews_tokenized$afinn2 <- textstat_valence(reviews_tokenized$tokens, afinn2)$sentiment

# replace all 0 values with na
reviews_tokenized[reviews_tokenized == 0] <- NA

# calculate mean scores for tokens per doc
afinn_scores <- reviews_tokenized %>%
  group_by(doc_id) %>% # group df by doc_id to get mean sentiment score
  summarise(total = mean(afinn2, na.rm = TRUE)) #%>% # calculate mean w/o regarding na values

# add afinn scores to df 
reviews_50$afinn <- afinn_scores$total

# different version to get plot 
reviews_tokenized %>%
  group_by(doc_id) %>% # group df by book and chapter to get sentiment per chapter
  #reviews_tokenized$sentiment = mean(afinn2, na.rm = TRUE) %>%
  summarise(sentiment = mean(afinn2, na.rm = TRUE)) %>% # calculate mean w/o regarding na values
  mutate(method = "AFINN") %>% # add column with method 
        ggplot(aes(doc_id, sentiment, fill = doc_id)) + # plot sentiment of books
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE) +
          #facet_wrap(~ doc_id, ncol = 2, scales = "free_x") +
          ggtitle("AFINN Reviews")
Warnung: Removed 1 rows containing missing values (position_stack).

LEXICODER

# apply lexicoder lexicon
reviews_50$lsd <- textstat_polarity(tokens(reviews_50$text), data_dictionary_LSD2015)$sentiment 
#series.df <- as.data.frame(series)

plot <- ggplot(reviews_50, aes(doc_id, lsd, fill = doc_id)) + # plot sentiment of books
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE) +
          #facet_wrap(~ doc_id, ncol = 2, scales = "free_x") +
          ggtitle("Lexicoder Reviews")
plot 

Vader

Statistics

# convert to binary results
reviews_50$afinn_binary[reviews_50$afinn > 0] <- "pos"
reviews_50$afinn_binary[reviews_50$afinn <= 0] <- "neg"

reviews_50$lsd_binary[reviews_50$lsd > 0] <- "pos"
reviews_50$lsd_binary[reviews_50$lsd <= 0] <- "neg"

reviews_50$vader_binary[reviews_50$vader > 0] <- "pos"
reviews_50$vader_binary[reviews_50$vader <= 0] <- "neg"

reviews_50$rating_binary[reviews_50$rating >= 3] <- "pos"
reviews_50$rating_binary[reviews_50$rating < 3] <- "neg"

# optionally: convert 0 = negative, 1 = positive
reviews_50[reviews_50 == "pos"] <- 1
reviews_50[reviews_50 == "neg"] <- 0
actual_values <- twitter_sentiment$rating_binary
predict_values <- twitter_sentiment$afinn_binary

# create confusion matrix 
confusion_matrix <- table(ACTUAL=actual_values, PREDICTED=predict_values)

# assign values from matrix to true/false positives/negatives
TN <- confusion_matrix[1]
FN <- confusion_matrix[2]
FP <- confusion_matrix[3]
TP <- confusion_matrix[4]

# calculate statistics
precision <- TP/(TP+FP)
accuracy <- (TP+TN)/(TP+TN+FP+FN)
recall <- TP/(TP+FN)
F1 <- (2*precision*recall)/(precision+recall)

precision
[1] NA
accuracy
[1] NA
recall
[1] NA
confusion_matrix[3]
[1] NA
get_statistics <- function(df) {
  statistics <- data.frame(matrix(ncol=4, nrow=0))
  x <- c("accuracy", "precision", "recall", "F1")
  colnames(statistics) <- x
  lex1 <- "afinn_binary"
  lex2 <- "lsd_binary"
  lex3 <- "vader_binary"
  gold <- "rating_binary"
  
  lexicons <- c(lex1,lex2,lex3)
  
  for(lex in lexicons){
    confusion_matrix <- table(ACTUAL=df[[gold]], PREDICTED=df[[lex]])
    TN <- confusion_matrix[1]
    FN <- confusion_matrix[2]
    FP <- confusion_matrix[3]
    TP <- confusion_matrix[4]
  
    # calculate statistics
    precision <- TP/(TP+FP)
    accuracy <- (TP+TN)/(TP+TN+FP+FN)
    recall <- TP/(TP+FN)
    F1 <- (2*precision*recall)/(precision+recall)
  
    # add to table
    output <- c(accuracy,precision, recall, F1)
    statistics[lex,] = rbind(statistics[[lex]], output)
    }
    
  return(statistics)
  
}

get_statistics(test)
LS0tCnRpdGxlOiAiQ29tcGFyaXNvbiBvZiBTZW50aW1lbnQgVG9vbHMgYWNyb3NzIERvbWFpbnMiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KYGBge3J9CiMgbG9hZCByZXF1aXJlZCBsaWJyYXJpZXMKCiMgdG8gdXNlIGhhcnJ5IHBvdHRlciBkYXRhc2V0CiMgZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJicmFkbGV5Ym9laG1rZS9oYXJyeXBvdHRlciIpCiMgZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJxdWFudGVkYS9xdWFudGVkYS5zZW50aW1lbnQiKQojIGRldnRvb2xzOjppbnN0YWxsX2dpdGh1YigicXVhbnRlZGEvcXVhbnRlZGEuY29ycG9yYSIpCgpsaWJyYXJ5KHF1YW50ZWRhKQpsaWJyYXJ5KHJlYWR0ZXh0KQpsaWJyYXJ5KGNvcnB1cykKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoc3RyaW5ncikKbGlicmFyeSh0aWR5dGV4dCkKbGlicmFyeShoYXJyeXBvdHRlcikKbGlicmFyeShkcGx5cikKbGlicmFyeShxdWFudGVkYS5zZW50aW1lbnQpCmxpYnJhcnkodmFkZXIpCmxpYnJhcnkoY2FyZXQpCmxpYnJhcnkocmVzaGFwZTIpCgoKcmVxdWlyZShxdWFudGVkYSkKcmVxdWlyZShxdWFudGVkYS5jb3Jwb3JhKQpyZXF1aXJlKHF1YW50ZWRhLnNlbnRpbWVudCkKI2xpYnJhcnkoInF1YW50ZWRhIiwgd2Fybi5jb25mbGljdHMgPSBGQUxTRSwgcXVpZXRseSA9IFRSVUUpCmBgYAoKIyAxLiBTdGVwOiBMb2FkIERhdGEgJiBMZXhpY29ucwpgYGB7cn0KIyBsb2FkIGRhdGFzZXRzCnJldmlld3MgPC0gcmVhZFJEUyhmaWxlPSJkYXRhc2V0cy9yZWRfcmV2aWV3LnJkcyIpCnR3aXR0ZXIgPC0gcmVhZFJEUyhmaWxlPSJkYXRhc2V0cy9yZWRfdHdpdHRlci5yZHMiKQpwYXJsdm90ZSA8LSByZWFkUkRTKGZpbGU9ImRhdGFzZXRzL3JlZF9wYXJsX3ZvdGUucmRzIikKCiMgbG9hZCBsZXhpY29uCmFmaW5uIDwtIGRhdGFfZGljdGlvbmFyeV9BRklOTgpgYGAKCmBgYHtyfQpyZXZpZXdzCnR3aXR0ZXIKcGFybHZvdGUKYGBgCiMgMi4gU3RlcDogUGVyZm9ybSBTZW50aW1lbnQgQW5hbHlzaXMKYGBge3J9CiMgY29tcGxldGUgZnVuY3Rpb24gdG8gZ2V0IGxleGljb24gc2NvcmVzCmdldF9zZW50aW1lbnQgPC0gZnVuY3Rpb24oZGF0YSwgbGV4aWNvbnMpewogIAogIGZvcihsZXggaW4gbGV4aWNvbnMpewogICAgaWYobGV4ID09ICJhZmlubiIpewogICAgICAKICAgIGRhdGEkYWZpbm4gPC0gdGV4dHN0YXRfdmFsZW5jZSh0b2tlbnMoZGF0YSR0ZXh0KSwgYWZpbm4sIG5vcm1hbGl6ZT0iZGljdGlvbmFyeSIpJHNlbnRpbWVudAogICAgfQogICAgCiAgICBpZihsZXggPT0gImxzZCIpewogICAgZGF0YSRsc2Q8LSB0ZXh0c3RhdF9wb2xhcml0eSh0b2tlbnMoZGF0YSR0ZXh0KSwgZGF0YV9kaWN0aW9uYXJ5X0xTRDIwMTUpJHNlbnRpbWVudAogICAgfQogICAgCiAgICBpZihsZXggPT0gInZhZGVyIil7CiAgICBkYXRhJHZhZGVyIDwtIHZhZGVyX2RmKGRhdGEkdGV4dCkkY29tcG91bmQKICAgIH0KICB9CiAgZGF0YVtpcy5uYShkYXRhKV0gPC0gMAogIHJldHVybihkYXRhKQp9CgpyZXZpZXdzX3NlbnRpbWVudCA8LSBnZXRfc2VudGltZW50KHJldmlld3MsIGMoImFmaW5uIiwibHNkIiwgInZhZGVyIikpCnR3aXR0ZXJfc2VudGltZW50IDwtIGdldF9zZW50aW1lbnQodHdpdHRlciwgYygiYWZpbm4iLCJsc2QiLCAidmFkZXIiKSkKcGFybHZvdGVfc2VudGltZW50IDwtIGdldF9zZW50aW1lbnQocGFybHZvdGUsIGMoImFmaW5uIiwibHNkIiwgInZhZGVyIikpCgpwYXJsdm90ZV9zZW50aW1lbnQKYGBgCiMgMy4gU3RlcDogQ2FsY3VsYXRlIFN0YXRpc3RpY3MKYGBge3J9CiMgY29udmVydCB0byBiaW5hcnkgcmVzdWx0cwpnZXRfYmluYXJ5IDwtIGZ1bmN0aW9uKGRhdGFmcmFtZXMsIHR5cGUpewogIGZvcihkZiBpbiBkYXRhZnJhbWVzKXsKICAgIGRmJGFmaW5uX2JpbmFyeVtkZiRhZmlubiA+IDBdIDwtICJwb3MiCiAgICBkZiRhZmlubl9iaW5hcnlbZGYkYWZpbm4gPD0gMF0gPC0gIm5lZyIKICAgICAgICAKICAgIGRmJGxzZF9iaW5hcnlbZGYkbHNkID4gMF0gPC0gInBvcyIKICAgIGRmJGxzZF9iaW5hcnlbZGYkbHNkIDw9IDBdIDwtICJuZWciCiAgICAgICAgCiAgICBkZiR2YWRlcl9iaW5hcnlbZGYkdmFkZXIgPiAwXSA8LSAicG9zIgogICAgZGYkdmFkZXJfYmluYXJ5W2RmJHZhZGVyIDw9IDBdIDwtICJuZWciCiAgICAgICAgCiAgICBkZiRyYXRpbmdfYmluYXJ5W2RmJHJhdGluZyA+PSAzXSA8LSAicG9zIgogICAgZGYkcmF0aW5nX2JpbmFyeVtkZiRyYXRpbmcgPCAzXSA8LSAibmVnIgogICAgaWYodHlwZSA9PSAiMDEiKXsKICAgICAgZGZbZGYgPT0gInBvcyJdIDwtIDEKICAgICAgZGZbZGYgPT0gIm5lZyJdIDwtIDAKICAgIH0KICB9CiAgcmV0dXJuKGRhdGFmcmFtZXMpCn0KCgp4IDwtIGdldF9iaW5hcnkobGlzdChyZXZpZXdzX3NlbnRpbWVudCwgdHdpdHRlcl9zZW50aW1lbnQsIHBhcmx2b3RlX3NlbnRpbWVudCksICIwMSIpCmBgYAoKYGBge3J9CnJldmlld3Nfc2VudGltZW50JGFmaW5uX2JpbmFyeVtyZXZpZXdzX3NlbnRpbWVudCRhZmlubiA+IDBdIDwtICIxIgpyZXZpZXdzX3NlbnRpbWVudCRhZmlubl9iaW5hcnlbcmV2aWV3c19zZW50aW1lbnQkYWZpbm4gPD0gMF0gPC0gIjAiCnJldmlld3Nfc2VudGltZW50JGxzZF9iaW5hcnlbcmV2aWV3c19zZW50aW1lbnQkbHNkID4gMF0gPC0gIjEiCnJldmlld3Nfc2VudGltZW50JGxzZF9iaW5hcnlbcmV2aWV3c19zZW50aW1lbnQkbHNkIDw9IDBdIDwtICIwIgpyZXZpZXdzX3NlbnRpbWVudCR2YWRlcl9iaW5hcnlbcmV2aWV3c19zZW50aW1lbnQkdmFkZXIgPiAwXSA8LSAiMSIKcmV2aWV3c19zZW50aW1lbnQkdmFkZXJfYmluYXJ5W3Jldmlld3Nfc2VudGltZW50JHZhZGVyIDw9IDBdIDwtICIwIgpyZXZpZXdzX3NlbnRpbWVudCRyYXRpbmdfYmluYXJ5W3Jldmlld3Nfc2VudGltZW50JHJhdGluZyA+PSAzXSA8LSAiMSIKcmV2aWV3c19zZW50aW1lbnQkcmF0aW5nX2JpbmFyeVtyZXZpZXdzX3NlbnRpbWVudCRyYXRpbmcgPCAzXSA8LSAiMCIKCnR3aXR0ZXJfc2VudGltZW50JGFmaW5uX2JpbmFyeVt0d2l0dGVyX3NlbnRpbWVudCRhZmlubiA+IDBdIDwtICIxIgp0d2l0dGVyX3NlbnRpbWVudCRhZmlubl9iaW5hcnlbdHdpdHRlcl9zZW50aW1lbnQkYWZpbm4gPD0gMF0gPC0gIjAiCnR3aXR0ZXJfc2VudGltZW50JGxzZF9iaW5hcnlbdHdpdHRlcl9zZW50aW1lbnQkbHNkID4gMF0gPC0gIjEiCnR3aXR0ZXJfc2VudGltZW50JGxzZF9iaW5hcnlbdHdpdHRlcl9zZW50aW1lbnQkbHNkIDw9IDBdIDwtICIwIgp0d2l0dGVyX3NlbnRpbWVudCR2YWRlcl9iaW5hcnlbdHdpdHRlcl9zZW50aW1lbnQkdmFkZXIgPiAwXSA8LSAiMSIKdHdpdHRlcl9zZW50aW1lbnQkdmFkZXJfYmluYXJ5W3R3aXR0ZXJfc2VudGltZW50JHZhZGVyIDw9IDBdIDwtICIwIgp0d2l0dGVyX3NlbnRpbWVudCRyYXRpbmdfYmluYXJ5W3R3aXR0ZXJfc2VudGltZW50JHJhdGluZyA9PSAiUG9zaXRpdmUiXSA8LSAiMSIKdHdpdHRlcl9zZW50aW1lbnQkcmF0aW5nX2JpbmFyeVt0d2l0dGVyX3NlbnRpbWVudCRyYXRpbmcgPT0gIk5lZ2F0aXZlIl0gPC0gIjAiCnR3aXR0ZXJfc2VudGltZW50JHJhdGluZ19iaW5hcnlbdHdpdHRlcl9zZW50aW1lbnQkcmF0aW5nID09ICJOZXV0cmFsIl0gPC0gTkEKCnBhcmx2b3RlX3NlbnRpbWVudCRhZmlubl9iaW5hcnlbcGFybHZvdGVfc2VudGltZW50JGFmaW5uID4gMF0gPC0gIjEiCnBhcmx2b3RlX3NlbnRpbWVudCRhZmlubl9iaW5hcnlbcGFybHZvdGVfc2VudGltZW50JGFmaW5uIDw9IDBdIDwtICIwIgpwYXJsdm90ZV9zZW50aW1lbnQkbHNkX2JpbmFyeVtwYXJsdm90ZV9zZW50aW1lbnQkbHNkID4gMF0gPC0gIjEiCnBhcmx2b3RlX3NlbnRpbWVudCRsc2RfYmluYXJ5W3Bhcmx2b3RlX3NlbnRpbWVudCRsc2QgPD0gMF0gPC0gIjAiCnBhcmx2b3RlX3NlbnRpbWVudCR2YWRlcl9iaW5hcnlbcGFybHZvdGVfc2VudGltZW50JHZhZGVyID4gMF0gPC0gIjEiCnBhcmx2b3RlX3NlbnRpbWVudCR2YWRlcl9iaW5hcnlbcGFybHZvdGVfc2VudGltZW50JHZhZGVyIDw9IDBdIDwtICIwIgoKIyBvcHRpb25hbGx5OiBjb252ZXJ0IDAgPSBuZWdhdGl2ZSwgMSA9IHBvc2l0aXZlCiNyZXZpZXdzXzUwW3Jldmlld3NfNTAgPT0gInBvcyJdIDwtIDEKI3Jldmlld3NfNTBbcmV2aWV3c181MCA9PSAibmVnIl0gPC0gMApgYGAKCmBgYHtyfQpwYXJsdm90ZV9zZW50aW1lbnQKYGBgCgoKYGBge3J9CmdldF9zdGF0aXN0aWNzIDwtIGZ1bmN0aW9uKGRmKSB7CiAgI3N0YXRpc3RpY3MgPC0gZGF0YS5mcmFtZShtYXRyaXgobmNvbD00LCBucm93PTApKQogICN4IDwtIGMoImFjY3VyYWN5IiwgInByZWNpc2lvbiIsICJyZWNhbGwiLCAiRjEiKQogICNjb2xuYW1lcyhzdGF0aXN0aWNzKSA8LSB4CiAgc3RhdGlzdGljcyA8LSBkYXRhLmZyYW1lKG1hdHJpeChuY29sPTEsIG5yb3c9MCkpCiAgY29sbmFtZXMoc3RhdGlzdGljcykgPC0gImFjY3VyYWN5IgogIAogIGxleDEgPC0gImFmaW5uX2JpbmFyeSIKICBsZXgyIDwtICJsc2RfYmluYXJ5IgogIGxleDMgPC0gInZhZGVyX2JpbmFyeSIKICBnb2xkIDwtICJyYXRpbmdfYmluYXJ5IgogIAogIGxleGljb25zIDwtIGMobGV4MSxsZXgyLGxleDMpCiAgCiAgZm9yKGxleCBpbiBsZXhpY29ucyl7CiAgICBjZiA8LSBjb25mdXNpb25NYXRyaXgoZmFjdG9yKGRmW1tsZXhdXSksZmFjdG9yKGRmW1tnb2xkXV0pKQogICAgYWNjdXJhY3kgPC0gY2ZbWzNdXVsxXQogICAgI2NvbmZ1c2lvbl9tYXRyaXggPC0gdGFibGUoQUNUVUFMPWRmW1tnb2xkXV0sIFBSRURJQ1RFRD1kZltbbGV4XV0pCiAgICAjVE4gPC0gY29uZnVzaW9uX21hdHJpeFsxXQogICAgI0ZOIDwtIGNvbmZ1c2lvbl9tYXRyaXhbMl0KICAgICNGUCA8LSBjb25mdXNpb25fbWF0cml4WzNdCiAgICAjVFAgPC0gY29uZnVzaW9uX21hdHJpeFs0XQogICAgCiAgICAjIGNhbGN1bGF0ZSBzdGF0aXN0aWNzCiAgICAjcHJlY2lzaW9uIDwtIFRQLyhUUCtGUCkKICAgICNhY2N1cmFjeSA8LSAoVFArVE4pLyhUUCtUTitGUCtGTikKICAgICNyZWNhbGwgPC0gVFAvKFRQK0ZOKQogICAgI0YxIDwtICgyKnByZWNpc2lvbipyZWNhbGwpLyhwcmVjaXNpb24rcmVjYWxsKQogIAogICAgIyBhZGQgdG8gdGFibGUKICAgICNvdXRwdXQgPC0gYyhhY2N1cmFjeSxwcmVjaXNpb24sIHJlY2FsbCwgRjEpCiAgICAjc3RhdGlzdGljc1tsZXgsXSA9IHJiaW5kKHN0YXRpc3RpY3NbW2xleF1dLCBvdXRwdXQpCiAgICBzdGF0aXN0aWNzW2xleCxdID0gcmJpbmQoc3RhdGlzdGljc1tbbGV4XV0sIGFjY3VyYWN5KQogICAgI3ByaW50KG91dHB1dCkKICAgIH0KICAgIAogIHJldHVybihzdGF0aXN0aWNzKQogIAp9CgpyZXZpZXdfc3RhdHMgPC0gZ2V0X3N0YXRpc3RpY3MocmV2aWV3c19zZW50aW1lbnQpCiN0d2l0dGVyX3N0YXRzIDwtIGdldF9zdGF0aXN0aWNzKHR3aXR0ZXJfc2VudGltZW50KQojcGFybHZvdGVfc3RhdHMgPC0gZ2V0X3N0YXRpc3RpY3MocGFybHZvdGVfc2VudGltZW50KQoKcmV2aWV3X3N0YXRzCmBgYAoKIyA0LiBTdGVwOiBQbG90IERhdGEgCmBgYHtyfQojIHBsb3QgY29sdW1ucwpyZXZpZXdzX2RmbSA8LSBtZWx0KGhlYWQocmV2aWV3c19zZW50aW1lbnQsNTApWyxjKCdpZCcsJ2FmaW5uJywnbHNkJywndmFkZXInKV0saWQudmFycyA9IDEpCgpyZXZpZXdzX3Bsb3QgPC0gZ2dwbG90KHJldmlld3NfZGZtLGFlcyh4ID0gaWQseSA9IHZhbHVlKSkgKyAKICAgICAgICAgICAgICAgIGdlb21fYmFyKGFlcyhmaWxsID0gdmFyaWFibGUpLHN0YXQgPSAiaWRlbnRpdHkiLHBvc2l0aW9uID0gImRvZGdlIikgKwogICAgICAgICAgICAgICAgZ2d0aXRsZSgiUmV2aWV3cyBTZW50aW1lbnRzIikKCgp0d2l0dGVyX2RmbSA8LSBtZWx0KGhlYWQodHdpdHRlcl9zZW50aW1lbnQsNTApWyxjKCdpZCcsJ2FmaW5uJywnbHNkJywndmFkZXInKV0saWQudmFycyA9IDEpCgp0d2l0dGVyX3Bsb3QgPC0gZ2dwbG90KHR3aXR0ZXJfZGZtLGFlcyh4ID0gaWQseSA9IHZhbHVlKSkgKyAKICAgICAgICAgICAgICAgIGdlb21fYmFyKGFlcyhmaWxsID0gdmFyaWFibGUpLHN0YXQgPSAiaWRlbnRpdHkiLHBvc2l0aW9uID0gImRvZGdlIikgKwogICAgICAgICAgICAgICAgZ2d0aXRsZSgiVHdpdHRlciBTZW50aW1lbnRzIikKCnBhcmx2b3RlX2RmbSA8LSBtZWx0KGhlYWQocGFybHZvdGVfc2VudGltZW50LDUwKVssYygnaWQnLCdhZmlubicsJ2xzZCcsJ3ZhZGVyJyldLGlkLnZhcnMgPSAxKQoKcGFybHZvdGVfcGxvdCA8LSBnZ3Bsb3QocGFybHZvdGVfZGZtLGFlcyh4ID0gaWQseSA9IHZhbHVlKSkgKyAKICAgICAgICAgICAgICAgIGdlb21fYmFyKGFlcyhmaWxsID0gdmFyaWFibGUpLHN0YXQgPSAiaWRlbnRpdHkiLHBvc2l0aW9uID0gImRvZGdlIikrCiAgICAgICAgICAgICAgICBmYWNldF93cmFwKH4gdmFyaWFibGUsIG5jb2wgPSAxLCBzY2FsZXM9ImZyZWVfeSIpKwogICAgICAgICAgICAgICAgZ2d0aXRsZSgiUGFybFZvdGUgU2VudGltZW50cyIpCgpyZXZpZXdzX3Bsb3QKdHdpdHRlcl9wbG90CnBhcmx2b3RlX3Bsb3QKYGBgCiMjIyBBRERJVElPTkFMCiMgSGFycnkgUG90dGVyIC0gRGF0YXNldApgYGB7cn0KIyBsb2FkIGhhcnJ5IHBvdHRlciBkYXRhc2V0IAp0aXRsZXMgPC0gYygiUGhpbG9zb3BoZXIncyBTdG9uZSIsICJDaGFtYmVyIG9mIFNlY3JldHMiLCAiUHJpc29uZXIgb2YgQXprYWJhbiIsCiAgICAgICAgICAgICJHb2JsZXQgb2YgRmlyZSIsICJPcmRlciBvZiB0aGUgUGhvZW5peCIsICJIYWxmLUJsb29kIFByaW5jZSIsCiAgICAgICAgICAgICJEZWF0aGx5IEhhbGxvd3MiKQoKYm9va3MgPC0gbGlzdChwaGlsb3NvcGhlcnNfc3RvbmUsIGNoYW1iZXJfb2Zfc2VjcmV0cywgcHJpc29uZXJfb2ZfYXprYWJhbiwKICAgICAgICAgICBnb2JsZXRfb2ZfZmlyZSwgb3JkZXJfb2ZfdGhlX3Bob2VuaXgsIGhhbGZfYmxvb2RfcHJpbmNlLAogICAgICAgICAgIGRlYXRobHlfaGFsbG93cykKICAKc2VyaWVzIDwtIHRpYmJsZSgpCgpmb3IoaSBpbiBzZXFfYWxvbmcodGl0bGVzKSkgewogICAgICAgIAogICAgICAgIGNsZWFuIDwtIHRpYmJsZShjaGFwdGVyID0gc2VxX2Fsb25nKGJvb2tzW1tpXV0pLAogICAgICAgICAgICAgICAgICAgICAgICB0ZXh0ID0gYm9va3NbW2ldXSkgJT4lCiAgICAgICAgICAgICAjdW5uZXN0X3Rva2Vucyh3b3JkLCB0ZXh0KSAlPiUKICAgICAgICAgICAgIG11dGF0ZShib29rID0gdGl0bGVzW2ldKSAlPiUKICAgICAgICAgICAgIHNlbGVjdChib29rLCBldmVyeXRoaW5nKCkpCgogICAgICAgIHNlcmllcyA8LSByYmluZChzZXJpZXMsIGNsZWFuKQp9CgpzZXJpZXMkYm9vayA8LSBmYWN0b3Ioc2VyaWVzJGJvb2ssIGxldmVscyA9IHJldih0aXRsZXMpKQoKc2VyaWVzCiNib29rX2dyb3VwcyA8LSBzZXJpZXMgJT4lIGdyb3VwX2J5KGJvb2ssIGNoYXB0ZXIpCiMgdG9rZW5pemUgaHAxCiNocDFfdG9rZW5pemVkIDwtIHRva2Vuc190b2xvd2VyKHRva2VucyhwaGlsb3NvcGhlcnNfc3RvbmUsIHJlbW92ZV9wdW5jdCA9IFRSVUUpKSAKYGBgCiMjIyBIYXJyeSBQb3R0ZXIgLSBBRklOTiBMZXhpY29uCmBgYHtyfQphZmlubl9ocDIgPC0gc2VyaWVzICU+JQogICAgICAgIGdyb3VwX2J5KGJvb2ssIGNoYXB0ZXIpICU+JSAjIGFkZCB3b3JkIGZvciBzaW5nbGUgd29yZCBzY29yZXMgCiAgICAgICAgaW5uZXJfam9pbihnZXRfc2VudGltZW50cygiYWZpbm4iKSkgJT4lCiAgICAgICAgZ3JvdXBfYnkoYm9vaywgY2hhcHRlcikgJT4lICMgYWRkIHdvcmQgZm9yIHNpbmdsZSB3b3JkIHNjb3JlcwogICAgICAgICNzdW1tYXJpc2Uoc2VudGltZW50ID0gc3VtKHZhbHVlKSkgJT4lCiAgICAgICAgc3VtbWFyaXNlKHNlbnRpbWVudCA9IG1lYW4odmFsdWUsIG5hLnJtID0gVFJVRSkpICU+JQogICAgICAgIG11dGF0ZShtZXRob2QgPSAiQUZJTk4iKSAgJT4lCiAgICAgICAgZ2dwbG90KGFlcyhjaGFwdGVyLCBzZW50aW1lbnQsIGZpbGwgPSBib29rKSkgKwogICAgICAgICAgZ2VvbV9iYXIoYWxwaGEgPSAwLjgsIHN0YXQgPSAiaWRlbnRpdHkiLCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgICAgICAgICBmYWNldF93cmFwKH4gYm9vaywgbmNvbCA9IDIsIHNjYWxlcyA9ICJmcmVlX3giKQoKYWZpbm5faHAyCgojZ2dzYXZlKHBsb3QgPSBhZmlubiwgd2lkdGggPSAxNSwgaGVpZ2h0ID0gMTUsIGRwaSA9IDMwMCwgZmlsZW5hbWUgPSAiYWZpbm5faHBfbWVhbi5wbmciKQpgYGAKCiMgTGV4aWNvZGVyOiBIUApgYGB7cn0KIyBzZWxlY3Qgb25seSB0aGUgIm5lZ2F0aXZlIiBhbmQgInBvc2l0aXZlIiBjYXRlZ29yaWVzCiNkYXRhX2RpY3Rpb25hcnlfTFNEMjAxNV9wb3NfbmVnIDwtIGRhdGFfZGljdGlvbmFyeV9MU0QyMDE1WzE6Ml0KI2hwMV9sc2QgPC0gdG9rZW5zX2xvb2t1cChocDFfdG9rZW5pemVkLCBkaWN0aW9uYXJ5ID0gZGF0YV9kaWN0aW9uYXJ5X0xTRDIwMTVfcG9zX25lZykKCnBvbGFyaXR5KGRhdGFfZGljdGlvbmFyeV9MU0QyMDE1KSA8LSAKICBsaXN0KHBvcyA9IGMoInBvc2l0aXZlIiwgIm5lZ19uZWdhdGl2ZSIpLCBuZWcgPSBjKCJuZWdhdGl2ZSIsICJuZWdfcG9zaXRpdmUiKSkKCmhwMV9sc2QgPC0gdGV4dHN0YXRfcG9sYXJpdHkoaHAxX3Rva2VuaXplZCwgZGF0YV9kaWN0aW9uYXJ5X0xTRDIwMTUpCgpocDFfbHNkX3Rva2VucyA8LSB0b2tlbnNfbG9va3VwKGhwMV90b2tlbml6ZWQsIGRhdGFfZGljdGlvbmFyeV9MU0QyMDE1LCBuZXN0ZWRfc2NvcGUgPSAiZGljdGlvbmFyeSIsIGV4Y2x1c2l2ZSA9IEZBTFNFKQpocDFfbHNkLmRmIDwtIGFzLmRhdGEuZnJhbWUubWF0cml4KGhwMV9sc2QpCmhwMV9sc2QuZGYkY2hhcHRlciA8LSAxOm5yb3coaHAxX2xzZC5kZikKCnBsb3QgPC0gZ2dwbG90KGhwMV9sc2QsIGFlcyh4ID1ocDFfbHNkLmRmJGNoYXB0ZXIsIHk9c2VudGltZW50KSkgKwogICAgICAgICAgZ2VvbV9iYXIoYWxwaGEgPSAwLjgsIHN0YXQgPSAiaWRlbnRpdHkiLCBzaG93LmxlZ2VuZCA9IEZBTFNFKQpwbG90ICsgeWxpbSgtMS4wLCAxLjApICsgbGFicyh5PSJzZW50aW1lbnQiLCB4ID0gImNoYXB0ZXIiKSArIGdndGl0bGUoIkhQMSAtIExleGljb2RlciIpCmBgYAojIEFGSU5OOiBIUApgYGB7cn0KaHAxX2FmaW5uIDwtIHRleHRzdGF0X3ZhbGVuY2UoaHAxX3Rva2VuaXplZCwgYWZpbm4sIG5vcm1hbGl6ZT0iZGljdGlvbmFyeSIpCgpocDFfYWZpbm4uZGYgPC0gYXMuZGF0YS5mcmFtZS5tYXRyaXgoaHAxX2FmaW5uKQpocDFfYWZpbm4uZGYkY2hhcHRlciA8LSAxOm5yb3coaHAxX2FmaW5uLmRmKQoKcGxvdCA8LSBnZ3Bsb3QoaHAxX2FmaW5uLmRmLCBhZXMoeCA9aHAxX2FmaW5uLmRmJGNoYXB0ZXIsIHk9c2VudGltZW50KSkgKwogICAgICAgICAgZ2VvbV9iYXIoYWxwaGEgPSAwLjgsIHN0YXQgPSAiaWRlbnRpdHkiLCBzaG93LmxlZ2VuZCA9IEZBTFNFKQpwbG90ICsgeWxpbSgtMS4wLCAxLjApICsgbGFicyh5PSJzZW50aW1lbnQiLCB4ID0gImNoYXB0ZXIiKSArIGdndGl0bGUoIkhQMSAtIEFGSU5OIikKYGBgCiMgVkFERVI6IEhQCmBgYHtyfQpnZXRfdmFkZXIocGhpbG9zb3BoZXJzX3N0b25lWzFdKQoKaHAxX3ZhZGVyIDwtIHZhZGVyX2RmKHBoaWxvc29waGVyc19zdG9uZSkKaHAxX3ZhZGVyJGNoYXB0ZXIgPC0gMTpucm93KGhwMV92YWRlcikKCnBsb3QgPC0gZ2dwbG90KGhwMV92YWRlciwgYWVzKHggPWNoYXB0ZXIsIHk9Y29tcG91bmQpKSArCiAgICAgICAgICBnZW9tX2JhcihhbHBoYSA9IDAuOCwgc3RhdCA9ICJpZGVudGl0eSIsIHNob3cubGVnZW5kID0gRkFMU0UpCnBsb3QgKyB5bGltKC01LjAsIDUuMCkgKyBsYWJzKHk9InNlbnRpbWVudCIsIHggPSAiY2hhcHRlciIpICsgZ2d0aXRsZSgiSFAxIC0gVkFERVIiKQpgYGAKIyBRVUFOVEVEQS5TRU5USU1FTlQKIyBBRklOTjogSFAKYGBge3J9CiMgV29yayB3aXRoIHF1YW50ZWRhLnNlbnRpbWVudCBvbiBIUCBjb3JwdXM6CiMgY29udmVydCB0aWJibGUgdG8gZGF0YWZyYW1lCnNlcmllcy5kZiA8LSBhcy5kYXRhLmZyYW1lKHNlcmllcykKCiMgdG9rZW5pemUgYm9va3MKc2VyaWVzX3Rva2VuaXplZCA8LSBzZXJpZXMuZGYgJT4lCiAgdW5uZXN0X3Rva2Vucyh0b2tlbnMsIHRleHQpCgojIGFwcGx5IGFmaW5uIGxleGljb24Kc2VyaWVzX3Rva2VuaXplZCRhZmlubiA8LSB0ZXh0c3RhdF92YWxlbmNlKHNlcmllc190b2tlbml6ZWQkdG9rZW5zLCBhZmlubikkc2VudGltZW50CgojIHJlcGxhY2UgYWxsIDAgdmFsdWVzIHdpdGggbmEKc2VyaWVzX3Rva2VuaXplZFtzZXJpZXNfdG9rZW5pemVkID09IDBdIDwtIE5BCgpzZXJpZXNfdG9rZW5pemVkICU+JQogIGdyb3VwX2J5KGJvb2ssIGNoYXB0ZXIpICU+JSAjIGdyb3VwIGRmIGJ5IGJvb2sgYW5kIGNoYXB0ZXIgdG8gZ2V0IHNlbnRpbWVudCBwZXIgY2hhcHRlcgogIHN1bW1hcmlzZShzZW50aW1lbnQgPSBtZWFuKGFmaW5uLCBuYS5ybSA9IFRSVUUpKSAlPiUgIyBjYWxjdWxhdGUgbWVhbiB3L28gcmVnYXJkaW5nIG5hIHZhbHVlcwogIG11dGF0ZShtZXRob2QgPSAiQUZJTk4iKSAlPiUgIyBhZGQgY29sdW1uIHdpdGggbWV0aG9kIAogICAgICAgIGdncGxvdChhZXMoY2hhcHRlciwgc2VudGltZW50LCBmaWxsID0gYm9vaykpICsgIyBwbG90IHNlbnRpbWVudCBvZiBib29rcwogICAgICAgICAgZ2VvbV9iYXIoYWxwaGEgPSAwLjgsIHN0YXQgPSAiaWRlbnRpdHkiLCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgICAgICAgICBmYWNldF93cmFwKH4gYm9vaywgbmNvbCA9IDIsIHNjYWxlcyA9ICJmcmVlX3giKSArCiAgICAgICAgICBnZ3RpdGxlKCJBRklOTiBIUCIpCmBgYAojIExleGljb2RlcjogSFAgIApgYGB7cn0KIyBXb3JrIHdpdGggcXVhbnRlZGEuc2VudGltZW50IG9uIEhQIGNvcnB1czoKIyBhcHBseSBsZXhpY29kZXIgbGV4aWNvbgpzZXJpZXMkbHNkIDwtIHRleHRzdGF0X3BvbGFyaXR5KHRva2VucyhzZXJpZXMkdGV4dCksIGRhdGFfZGljdGlvbmFyeV9MU0QyMDE1KSRzZW50aW1lbnQgCgojc2VyaWVzLmRmIDwtIGFzLmRhdGEuZnJhbWUoc2VyaWVzKQoKcGxvdCA8LSBnZ3Bsb3Qoc2VyaWVzLCBhZXMoY2hhcHRlciwgbHNkLCBmaWxsID0gYm9vaykpICsgIyBwbG90IHNlbnRpbWVudCBvZiBib29rcwogICAgICAgICAgZ2VvbV9iYXIoYWxwaGEgPSAwLjgsIHN0YXQgPSAiaWRlbnRpdHkiLCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgICAgICAgICBmYWNldF93cmFwKH4gYm9vaywgbmNvbCA9IDIsIHNjYWxlcyA9ICJmcmVlX3giKSArCiAgICAgICAgICBnZ3RpdGxlKCJMZXhpY29kZXIgSFAiKQpwbG90IApgYGAKCiMgVmFkZXI6IEhQCmBgYHtyfQojIGFwcGx5IHZhZGVyIGxleGljb24gdG8gYWxsIEhQIGJvb2tzCnNlcmllcyR2YWRlciA8LSB2YWRlcl9kZihzZXJpZXMkdGV4dCkkY29tcG91bmQKCiNzZXJpZXMuZGYgPC0gYXMuZGF0YS5mcmFtZShzZXJpZXMpCgpwbG90IDwtIGdncGxvdChzZXJpZXMsIGFlcyhjaGFwdGVyLCBsc2QsIGZpbGwgPSBib29rKSkgKyAjIHBsb3Qgc2VudGltZW50IG9mIGJvb2tzCiAgICAgICAgICBnZW9tX2JhcihhbHBoYSA9IDAuOCwgc3RhdCA9ICJpZGVudGl0eSIsIHNob3cubGVnZW5kID0gRkFMU0UpICsKICAgICAgICAgIGZhY2V0X3dyYXAofiBib29rLCBuY29sID0gMiwgc2NhbGVzID0gImZyZWVfeCIpICsKICAgICAgICAgIGdndGl0bGUoIlZBREVSIEhQIikKcGxvdCAKYGBgCgojIFJFVklFV1MgREFUQVNFVApgYGB7cn0KIyBsb2FkIGRhdGFzZXQKcmV2aWV3cyA8LSByZWFkdGV4dCgiZGF0YXNldHMvZ29vZHJlYWRzX3Jldmlld3NfY2hpbGRyZW5fMi5qc29uIiwgdGV4dF9maWVsZCA9ICJyZXZpZXdfdGV4dCIpCgojIGNvbnZlcnQgdG8gZGF0YWZyYW1lCnJldmlld3MuZGYgPC0gYXMuZGF0YS5mcmFtZShyZXZpZXdzKQoKIyBhZGQgZG9jX2lkIChpLmUuIGFjY29yZGluZyB0byBpbmRleCkKcmV2aWV3cy5kZiRkb2NfaWQgPC0gMTpucm93KHJldmlld3MuZGYpCmBgYAoKIyMjIFNhbXBsZSBEYXRhc2V0CmBgYHtyfQojIGdldCByYW5kb20gc2FtcGxlIG9mIDUwIHJldmlld3MgCnJldmlld3Nfc2FtcGxlIDwtIHJldmlld3MuZGZbc2FtcGxlKDE6bnJvdyhyZXZpZXdzLmRmKSwgNTAsCiAgIHJlcGxhY2U9RkFMU0UpLF0KCiMgZ2V0IGZpcnN0IDUwIHJvd3Mgb2YgZGF0YSAKcmV2aWV3c181MCA8LSBoZWFkKHJldmlld3MuZGYsNTApCnJldmlld3NfNTAgPSBzdWJzZXQocmV2aWV3c181MCwgc2VsZWN0ID0gYyhkb2NfaWQsdGV4dCxyYXRpbmcpKQpgYGAKIyMjIEdldCBUcmFuc2xhdGlvbnMgb2YgRGF0YXNldCAKYGBge3J9CiMgZWl0aGVyIHZpYSBjb3JwdXMgCnJldmlld3MuY29ycHVzIDwtIGNvcnB1cyhyZXZpZXdzKQpkb2N2YXJzKHJldmlld3MuY29ycHVzLCAibGFuZ3VhZ2UiKSA8LSB0ZXh0Y2F0KHJldmlld3MuY29ycHVzKQpyZXZpZXdzX2VuIDwtIGNvcnB1c19zdWJzZXQocmV2aWV3cy5jb3JwdXMsIGxhbmd1YWdlID09ICJlbmdsaXNoIiwgZHJvcF9kb2NpZCA9IFRSVUUpCgojIG9yIHZpYSBkYXRhZnJhbWUgbG9naWMKcmV2aWV3cy5kZiRsYW5ndWFnZSA8LSB0ZXh0Y2F0KHJldmlld3MuZGYkdGV4dCkKYGBgCgojIyMgU2VudGltZW50IEFuYWx5c2lzIG9uIFJldmlld3MgRGF0YXNldAoKIyMjIyBBRklOTgpgYGB7cn0KIyBBZmlubgojIHRva2VuaXplIApyZXZpZXdzX3Rva2VuaXplZCA8LSByZXZpZXdzXzUwICU+JQogIHVubmVzdF90b2tlbnModG9rZW5zLCB0ZXh0KQoKIyBhcHBseSBhZmlubiBsZXhpY29uCnJldmlld3NfdG9rZW5pemVkJGFmaW5uIDwtIHRleHRzdGF0X3ZhbGVuY2UocmV2aWV3c190b2tlbml6ZWQkdG9rZW5zLCBhZmlubikkc2VudGltZW50CgojIHJlcGxhY2UgYWxsIDAgdmFsdWVzIHdpdGggbmEKcmV2aWV3c190b2tlbml6ZWRbcmV2aWV3c190b2tlbml6ZWQgPT0gMF0gPC0gTkEKCiMgY2FsY3VsYXRlIG1lYW4gc2NvcmVzIGZvciB0b2tlbnMgcGVyIGRvYwphZmlubl9zY29yZXMgPC0gcmV2aWV3c190b2tlbml6ZWQgJT4lCiAgZ3JvdXBfYnkoZG9jX2lkKSAlPiUgIyBncm91cCBkZiBieSBkb2NfaWQgdG8gZ2V0IG1lYW4gc2VudGltZW50IHNjb3JlCiAgc3VtbWFyaXNlKHRvdGFsID0gbWVhbihhZmlubiwgbmEucm0gPSBUUlVFKSkgIyU+JSAjIGNhbGN1bGF0ZSBtZWFuIHcvbyByZWdhcmRpbmcgbmEgdmFsdWVzCgojIGFkZCBhZmlubiBzY29yZXMgdG8gZGYgCnJldmlld3NfNTAkYWZpbm4gPC0gYWZpbm5fc2NvcmVzJHRvdGFsCgojIGRpZmZlcmVudCB2ZXJzaW9uIHRvIGdldCBwbG90IApyZXZpZXdzX3Rva2VuaXplZCAlPiUKICBncm91cF9ieShkb2NfaWQpICU+JSAjIGdyb3VwIGRmIGJ5IGJvb2sgYW5kIGNoYXB0ZXIgdG8gZ2V0IHNlbnRpbWVudCBwZXIgY2hhcHRlcgogICNyZXZpZXdzX3Rva2VuaXplZCRzZW50aW1lbnQgPSBtZWFuKGFmaW5uLCBuYS5ybSA9IFRSVUUpICU+JQogIHN1bW1hcmlzZShzZW50aW1lbnQgPSBtZWFuKGFmaW5uLCBuYS5ybSA9IFRSVUUpKSAlPiUgIyBjYWxjdWxhdGUgbWVhbiB3L28gcmVnYXJkaW5nIG5hIHZhbHVlcwogIG11dGF0ZShtZXRob2QgPSAiQUZJTk4iKSAlPiUgIyBhZGQgY29sdW1uIHdpdGggbWV0aG9kIAogICAgICAgIGdncGxvdChhZXMoZG9jX2lkLCBzZW50aW1lbnQsIGZpbGwgPSBkb2NfaWQpKSArICMgcGxvdCBzZW50aW1lbnQgb2YgYm9va3MKICAgICAgICAgIGdlb21fYmFyKGFscGhhID0gMC44LCBzdGF0ID0gImlkZW50aXR5Iiwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogICAgICAgICAgI2ZhY2V0X3dyYXAofiBkb2NfaWQsIG5jb2wgPSAyLCBzY2FsZXMgPSAiZnJlZV94IikgKwogICAgICAgICAgZ2d0aXRsZSgiQUZJTk4gUmV2aWV3cyIpCmBgYAojIyMjIExFWElDT0RFUgpgYGB7cn0KIyBhcHBseSBsZXhpY29kZXIgbGV4aWNvbgpyZXZpZXdzXzUwJGxzZCA8LSB0ZXh0c3RhdF9wb2xhcml0eSh0b2tlbnMocmV2aWV3c181MCR0ZXh0KSwgZGF0YV9kaWN0aW9uYXJ5X0xTRDIwMTUpJHNlbnRpbWVudCAKI3Nlcmllcy5kZiA8LSBhcy5kYXRhLmZyYW1lKHNlcmllcykKCnBsb3QgPC0gZ2dwbG90KHJldmlld3NfNTAsIGFlcyhkb2NfaWQsIGxzZCwgZmlsbCA9IGRvY19pZCkpICsgIyBwbG90IHNlbnRpbWVudCBvZiBib29rcwogICAgICAgICAgZ2VvbV9iYXIoYWxwaGEgPSAwLjgsIHN0YXQgPSAiaWRlbnRpdHkiLCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgICAgICAgICAjZmFjZXRfd3JhcCh+IGRvY19pZCwgbmNvbCA9IDIsIHNjYWxlcyA9ICJmcmVlX3giKSArCiAgICAgICAgICBnZ3RpdGxlKCJMZXhpY29kZXIgUmV2aWV3cyIpCnBsb3QgCmBgYAojIyMjIFZhZGVyCmBgYHtyfQpyZXZpZXdzXzUwJHZhZGVyIDwtIHZhZGVyX2RmKHJldmlld3NfNTAkdGV4dCkkY29tcG91bmQKCnBsb3QgPC0gZ2dwbG90KHJldmlld3NfNTAsIGFlcyhkb2NfaWQsIHZhZGVyLCBmaWxsID0gZG9jX2lkKSkgKyAjIHBsb3Qgc2VudGltZW50IG9mIGJvb2tzCiAgICAgICAgICBnZW9tX2JhcihhbHBoYSA9IDAuOCwgc3RhdCA9ICJpZGVudGl0eSIsIHNob3cubGVnZW5kID0gRkFMU0UpICsKICAgICAgICAgICNmYWNldF93cmFwKH4gZG9jX2lkLCBuY29sID0gMiwgc2NhbGVzID0gImZyZWVfeCIpICsKICAgICAgICAgIGdndGl0bGUoIlZhZGVyIFJldmlld3MiKQpwbG90IApgYGAKIyBTdGF0aXN0aWNzIApgYGB7cn0KIyBjb252ZXJ0IHRvIGJpbmFyeSByZXN1bHRzCnJldmlld3NfNTAkYWZpbm5fYmluYXJ5W3Jldmlld3NfNTAkYWZpbm4gPiAwXSA8LSAicG9zIgpyZXZpZXdzXzUwJGFmaW5uX2JpbmFyeVtyZXZpZXdzXzUwJGFmaW5uIDw9IDBdIDwtICJuZWciCgpyZXZpZXdzXzUwJGxzZF9iaW5hcnlbcmV2aWV3c181MCRsc2QgPiAwXSA8LSAicG9zIgpyZXZpZXdzXzUwJGxzZF9iaW5hcnlbcmV2aWV3c181MCRsc2QgPD0gMF0gPC0gIm5lZyIKCnJldmlld3NfNTAkdmFkZXJfYmluYXJ5W3Jldmlld3NfNTAkdmFkZXIgPiAwXSA8LSAicG9zIgpyZXZpZXdzXzUwJHZhZGVyX2JpbmFyeVtyZXZpZXdzXzUwJHZhZGVyIDw9IDBdIDwtICJuZWciCgpyZXZpZXdzXzUwJHJhdGluZ19iaW5hcnlbcmV2aWV3c181MCRyYXRpbmcgPj0gM10gPC0gInBvcyIKcmV2aWV3c181MCRyYXRpbmdfYmluYXJ5W3Jldmlld3NfNTAkcmF0aW5nIDwgM10gPC0gIm5lZyIKCiMgb3B0aW9uYWxseTogY29udmVydCAwID0gbmVnYXRpdmUsIDEgPSBwb3NpdGl2ZQpyZXZpZXdzXzUwW3Jldmlld3NfNTAgPT0gInBvcyJdIDwtIDEKcmV2aWV3c181MFtyZXZpZXdzXzUwID09ICJuZWciXSA8LSAwCmBgYAoKYGBge3J9CmFjdHVhbF92YWx1ZXMgPC0gdHdpdHRlcl9zZW50aW1lbnQkcmF0aW5nX2JpbmFyeQpwcmVkaWN0X3ZhbHVlcyA8LSB0d2l0dGVyX3NlbnRpbWVudCRhZmlubl9iaW5hcnkKCiMgY3JlYXRlIGNvbmZ1c2lvbiBtYXRyaXggCmNvbmZ1c2lvbl9tYXRyaXggPC0gdGFibGUoQUNUVUFMPWFjdHVhbF92YWx1ZXMsIFBSRURJQ1RFRD1wcmVkaWN0X3ZhbHVlcykKCiMgYXNzaWduIHZhbHVlcyBmcm9tIG1hdHJpeCB0byB0cnVlL2ZhbHNlIHBvc2l0aXZlcy9uZWdhdGl2ZXMKVE4gPC0gY29uZnVzaW9uX21hdHJpeFsxXQpGTiA8LSBjb25mdXNpb25fbWF0cml4WzJdCkZQIDwtIGNvbmZ1c2lvbl9tYXRyaXhbM10KVFAgPC0gY29uZnVzaW9uX21hdHJpeFs0XQoKIyBjYWxjdWxhdGUgc3RhdGlzdGljcwpwcmVjaXNpb24gPC0gVFAvKFRQK0ZQKQphY2N1cmFjeSA8LSAoVFArVE4pLyhUUCtUTitGUCtGTikKcmVjYWxsIDwtIFRQLyhUUCtGTikKRjEgPC0gKDIqcHJlY2lzaW9uKnJlY2FsbCkvKHByZWNpc2lvbityZWNhbGwpCgpwcmVjaXNpb24KYWNjdXJhY3kKcmVjYWxsCmNvbmZ1c2lvbl9tYXRyaXhbM10KYGBgCmBgYHtyfQp0d2l0dGVyX3NlbnRpbWVudFt0d2l0dGVyX3NlbnRpbWVudCA9PSBOQV0gPC0gMAoKdHdpdHRlcl9zZW50aW1lbnRbaXMubmEodHdpdHRlcl9zZW50aW1lbnQpXSA8LSAwCgp0d2l0dGVyX3NlbnRpbWVudApgYGAKCmBgYHtyfQpnZXRfc3RhdGlzdGljcyA8LSBmdW5jdGlvbihkZikgewogIHN0YXRpc3RpY3MgPC0gZGF0YS5mcmFtZShtYXRyaXgobmNvbD00LCBucm93PTApKQogIHggPC0gYygiYWNjdXJhY3kiLCAicHJlY2lzaW9uIiwgInJlY2FsbCIsICJGMSIpCiAgY29sbmFtZXMoc3RhdGlzdGljcykgPC0geAogIGxleDEgPC0gImFmaW5uX2JpbmFyeSIKICBsZXgyIDwtICJsc2RfYmluYXJ5IgogIGxleDMgPC0gInZhZGVyX2JpbmFyeSIKICBnb2xkIDwtICJyYXRpbmdfYmluYXJ5IgogIAogIGxleGljb25zIDwtIGMobGV4MSxsZXgyLGxleDMpCiAgCiAgZm9yKGxleCBpbiBsZXhpY29ucyl7CiAgICBjb25mdXNpb25fbWF0cml4IDwtIHRhYmxlKEFDVFVBTD1kZltbZ29sZF1dLCBQUkVESUNURUQ9ZGZbW2xleF1dKQogICAgVE4gPC0gY29uZnVzaW9uX21hdHJpeFsxXQogICAgRk4gPC0gY29uZnVzaW9uX21hdHJpeFsyXQogICAgRlAgPC0gY29uZnVzaW9uX21hdHJpeFszXQogICAgVFAgPC0gY29uZnVzaW9uX21hdHJpeFs0XQogIAogICAgIyBjYWxjdWxhdGUgc3RhdGlzdGljcwogICAgcHJlY2lzaW9uIDwtIFRQLyhUUCtGUCkKICAgIGFjY3VyYWN5IDwtIChUUCtUTikvKFRQK1ROK0ZQK0ZOKQogICAgcmVjYWxsIDwtIFRQLyhUUCtGTikKICAgIEYxIDwtICgyKnByZWNpc2lvbipyZWNhbGwpLyhwcmVjaXNpb24rcmVjYWxsKQogIAogICAgIyBhZGQgdG8gdGFibGUKICAgIG91dHB1dCA8LSBjKGFjY3VyYWN5LHByZWNpc2lvbiwgcmVjYWxsLCBGMSkKICAgIHN0YXRpc3RpY3NbbGV4LF0gPSByYmluZChzdGF0aXN0aWNzW1tsZXhdXSwgb3V0cHV0KQogICAgfQogICAgCiAgcmV0dXJuKHN0YXRpc3RpY3MpCiAgCn0KCmdldF9zdGF0aXN0aWNzKHRlc3QpCmBgYAoKCgoKCgoK